package com.flex.showdoc4j.factory;

import cn.hutool.core.util.ReflectUtil;
import org.apache.commons.lang.math.RandomUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author ganlt
 * @since 2019/07/22 19:36
 */
public class MockUtil {
    private static final Boolean[] bools = new Boolean[]{true, false};

    private static final char[] words = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
            'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};


    private static final int MAX_COLLECTION_LENGTH = 10;

    private static final int MAX_STRING_LENGTH = 15;

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static <T> T mock(Class<T> clazz) {
        if (clazz == Character.class || clazz == Character.TYPE) {
            return (T) (Character) words[nextInt(0, words.length)];
        } else if (clazz == Boolean.class || clazz == Boolean.TYPE) {
            return (T) (Boolean) bools[nextInt(0, bools.length)];
        } else if (clazz == Long.class || clazz == Long.TYPE) {
            return (T) (Long) nextLong(0, Integer.MAX_VALUE);
        } else if (clazz == Integer.class || clazz == Integer.TYPE) {
            return (T) (Integer) nextInt(0, Integer.MAX_VALUE);
        } else if (clazz == Short.class || clazz == Short.TYPE) {
            return (T) (Short) new Integer(nextInt(0, 127)).shortValue();
        } else if (clazz == Float.class || clazz == Float.TYPE) {
            return (T) (Float) nextFloat(0, Float.MAX_VALUE);
        } else if (clazz == Double.class || clazz == Double.TYPE) {
            return (T) (Double) nextDouble(0, Double.MAX_VALUE);
        } else if (clazz == String.class) {
            return (T) randString(nextInt(0, MAX_STRING_LENGTH));
        } else if (clazz.isEnum()) {
            Object[] enumConstants = clazz.getEnumConstants();
            for (Object enumConstant : enumConstants) {
                return (T) enumConstant;
            }
        }

        try {

            T instance = clazz.newInstance();

            for (Field f : ReflectUtil.getFields(clazz)) {
                f.setAccessible(true);
                if (Modifier.isFinal(f.getModifiers())) {
                    continue;
                }

                if (f.getType() == Character.TYPE) {
                    f.setChar(instance, words[nextInt(0, words.length)]);
                } else if (f.getType() == Character.class) {
                    f.set(instance, words[nextInt(0, words.length)]);
                } else if (f.getType() == Boolean.TYPE) {
                    f.setBoolean(instance, bools[nextInt(0, bools.length)]);
                } else if (f.getType() == Boolean.class) {
                    f.set(instance, bools[nextInt(0, bools.length)]);
                } else if (f.getType() == Long.TYPE) {
                    f.setLong(instance, nextLong(1, 10000));
                } else if (f.getType() == Long.class) {
                    f.set(instance, nextLong(1, 10000));
                } else if (f.getType() == Integer.TYPE) {
                    f.setInt(instance, nextInt(1, 10000));
                } else if (f.getType() == Integer.class) {
                    f.set(instance, nextInt(1, 10000));
                } else if (f.getType() == Short.TYPE) {
                    f.setShort(instance, new Integer(nextInt(0, 127)).shortValue());
                } else if (f.getType() == Short.class) {
                    f.set(instance, new Integer(nextInt(0, 127)).shortValue());
                } else if (f.getType() == Float.TYPE) {
                    f.setFloat(instance, RandomUtils.nextFloat());
                } else if (f.getType() == Float.class) {
                    f.set(instance, RandomUtils.nextFloat());
                } else if (f.getType() == Double.TYPE) {
                    f.setDouble(instance, RandomUtils.nextDouble());
                } else if (f.getType() == Double.class) {
                    f.set(instance, RandomUtils.nextDouble());
                } else if (f.getType() == String.class) {
                    f.set(instance, randString(nextInt(1, MAX_STRING_LENGTH)));
                } else if (f.getType() == List.class) {
                    int size = nextInt(1, MAX_COLLECTION_LENGTH);
                    List<Object> list = new ArrayList<Object>(size);
                    ParameterizedType pt = null;
                    for (int i = 0; i < size; i++) {
                        pt = (ParameterizedType) f.getGenericType();
                        Type type1 = pt.getActualTypeArguments()[0];
                        if (!clazz.isAssignableFrom((Class<?>) type1)) {
                            list.add(mock((Class) type1));
                        }
                    }
                    f.set(instance, list);
                } else if (f.getType() == Map.class) {
                    int size = nextInt(1, MAX_COLLECTION_LENGTH);
                    Map<Object, Object> map = new HashMap<Object, Object>();
                    ParameterizedType pt = null;
                    for (int i = 0; i < size; i++) {
                        pt = (ParameterizedType) f.getGenericType();
                        map.put(mock((Class) pt.getActualTypeArguments()[0]),
                                mock((Class) pt.getActualTypeArguments()[1]));
                    }
                    f.set(instance, map);
                } else if (f.getType() == Date.class) {
                    f.set(instance, new Date());
                } else if (f.getType().isEnum()) {
                    Object[] enumConstants = f.getType().getEnumConstants();
                    for (Object enumConstant : enumConstants) {
                        f.set(instance, enumConstant);
                        break;
                    }
                } else {
                    if (!f.getType().isAssignableFrom(clazz)) {
                        f.set(instance, mock(f.getType()));
                    }
                }
            }

            return instance;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private static Integer nextInt(int start, int end) {
        Random random = new Random();
        Integer randNum =  random.nextInt(end - start ) + start;
        return randNum;
    }

    private static Long nextLong(int start, int end) {
        Random random = new Random();
        Integer randNum =  random.nextInt(end - start ) + start;
        return randNum.longValue();
    }

    private static Float nextFloat(float start, float end) {
        Float randNum = RandomUtils.nextFloat();
        return randNum;
    }

    private static Double nextDouble(double start, double end) {
        Double randNum = RandomUtils.nextDouble();
        return randNum;
    }

    private static String randString(int count) {
        if (count == 0) {
            count = 1;
        }

        int length = words.length;
        char[] text = new char[count];
        for (int i = 0; i < count; i++) {
            text[i] = words[nextInt(0, length)];
        }

        return new String(text);
    }

}
